#ifndef __M_SINK_H_
#define __M_SINK_H_
/*
日志落地模块
    1. 抽象落地基类
    2. 派生子类，用于不同的落地方向
    3. 使用工厂模式，便于用户拓展
*/

#include "util.hpp"
#include <memory>
#include <sstream>
#include <fstream>
#include <cassert>

namespace log
{
    class Sink
    {
    public:
        using ptr = std::shared_ptr<Sink>;
        virtual void log(const char *data, size_t len) = 0;
        Sink() {}
        virtual ~Sink() {}
    };
    // 1. 标注输出
    class StdoutSink : public Sink
    {
    public:
        void log(const char *data, size_t len) override
        {
            // 将data里len长度的数据输入到标注输出
            // 不能用<<，因为<<没法指定大小，还有日志输出不一定都是字符串
            std::cout.write(data, len);
        }
    };
    // 2. 指定文件
    class FileSink : public Sink
    {
    public:
        FileSink(const std::string &pathName) : _pathName(pathName)
        {
            // 1. 判断文件目录是否存在，并创建
            log::util::File::createDirectory(log::util::File::Path(_pathName));
            // 2. 打开文件流
            _ofs.open(_pathName, std::ios::binary | std::ios::app); // 二进制追加
            assert(_ofs.is_open());
        }
        void log(const char *data, size_t len) override
        {
            _ofs.write(data, len);
            assert(_ofs.good());
        }

    private:
        std::string _pathName; // 指定放置的文件名
        std::ofstream _ofs;    // 操作句柄，每次将数据放入文件流中
    };
    // 3. 滚动输出  （以大小进行滚动）
    class RollBySinkSize : public Sink
    {
    public:
        RollBySinkSize(const std::string &basename, size_t max_fsize)
            : _basename(basename), _max_fsize(max_fsize), _cur_fsize(0), _count(0)
        {
            // 创建日志文件拓展名
            std::string pathname = createNewFile();
            // 1. 创建日志文件路径
            util::File::createDirectory(util::File::Path(pathname));
            // 2. 创建并打开日志文件
            _ofs.open(pathname, std::ios::binary | std::ios::app);
            assert(_ofs.is_open());
        }
        // 根据大小进行新文件创建
        void log(const char *data, size_t len) override
        {
            _cur_fsize += len;
            // 文件过大就新建
            if (_cur_fsize >= _max_fsize)
            {
                _ofs.close(); // 一定要关闭原来的文件，否则会造成资源泄露
                std::string pathname = createNewFile();
                _ofs.open(pathname, std::ios::binary | std::ios::app);
                assert(_ofs.is_open());
                _cur_fsize = 0;
            }
            _ofs.write(data, len);
            assert(_ofs.good());
        }

    private:
        std::string createNewFile() // 以时间来构造文件拓展名
        {
            struct tm t;
            time_t now = log::util::Date::now();
            localtime_r(&now, &t);
            std::stringstream fs;
            // 年月日时分秒
            fs << _basename;
            fs << t.tm_year + 1900;
            fs << t.tm_mon + 1;
            fs << t.tm_mday;
            fs << t.tm_hour;
            fs << t.tm_min;
            fs << t.tm_sec;
            fs << "-";
            fs << _count++;
            fs << ".log";
            return fs.str();
        }

    private:
        std::string _basename; // 基础文件名 ./logs/base-    -->  ./log/base-当前时间.log
        std::ofstream _ofs;    // 操作句柄，每次将数据放入文件流中
        size_t _max_fsize;     // 最大文件大小
        size_t _cur_fsize;     // 当前文件大小
        int _count;            // 文件编号
    };

    // 工厂模式
    // 日志落地工厂
    class SinkFactory
    {
    public:
        template <class SinkType, class... Args>
        static Sink::ptr create(Args &&...args)
        {
            // 这样不利于拓展
            // return std::make_shared<StdoutSink>();
            // 模板，但是类型的参数不统一
            // return std::make_shared<SinkType>();
            // 可变模板参数
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };
}

#endif